package jinyilw.swing.rlaf.utils;

import jinyilw.swing.graphics.GraphicsTools;
import jinyilw.swing.graphics.RenderingConfig;
import jinyilw.swing.rlaf.UITools;
import jinyilw.swing.rlaf.theme.RThemeColor;
import jinyilw.swing.rlaf.ui.RFocusManager;
import jinyilw.swing.visuals.Jcomp.JMouseActComp;
import jinyilw.swing.visuals.border.RichTitleBorder;

import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

/**
 * @author Jinyi
 */
public class RBorderFactory
{
	private static final Insets TEXT_INSETS = new Insets(3, 5, 3, 5);
	public static final int defaultArc = 12;
	// private static Color hiBackColor = new Color(226, 228, 232);
	// private static Color loBackColor = new Color(192, 195, 200);

	private static Border defaultBorder;
	private static Border activeBorder;
	private static Border noShadowBorder;
	private static Border focusBorder;
	private static Border emptyBorder;
	private static Border simpleRoundBorder;

	private RBorderFactory()
	{
	}

	public static Border getDefaultBorder()
	{
		if (defaultBorder == null)
			defaultBorder = new RoundBorder(defaultArc);
		return defaultBorder;
	}

	public static Border getActiveBorder()
	{
		if (activeBorder == null)
			activeBorder = new RoundBorder(defaultArc, true);
		return activeBorder;
	}

	public static void setToActiveBorder(final JComponent c, JComponent... cs)
	{
		if (c == null)
			return;
		c.setBorder(getDefaultBorder());
		MouseAdapter mouseAdapter;
		if (cs.length > 0)
		{
			mouseAdapter = new MouseAdapter()
			{
				@Override
				public void mouseExited(MouseEvent e)
				{
					Point p = SwingUtilities.convertPoint(e.getComponent(),
							e.getX(), e.getY(), c);
					if (c.isEnabled() && !c.contains(p))
						c.setBorder(getDefaultBorder());
				}
				@Override
				public void mouseEntered(MouseEvent e)
				{
					if (c.isEnabled())
						c.setBorder(getActiveBorder());
				}
			};
			for (JComponent co : cs)
				co.addMouseListener(mouseAdapter);
		} else
			mouseAdapter = new MouseAdapter()
			{
				@Override
				public void mouseExited(MouseEvent e)
				{
					if (c.isEnabled())
						c.setBorder(getDefaultBorder());
				}
				@Override
				public void mouseEntered(MouseEvent e)
				{
					if (c.isEnabled())
						c.setBorder(getActiveBorder());
				}
				@Override
				public void mouseClicked(MouseEvent e)
				{
					if (c.isEnabled())
						c.setBorder(getDefaultBorder());
				}
			};
		c.addMouseListener(mouseAdapter);
	}

	public static void setToActiveBorder2(final JComponent c)
	{
		if (c == null)
			return;
		c.setBorder(getDefaultBorder());
		if (c instanceof JMouseActComp)
		{
			((JMouseActComp) c).setRolloverEnable(true);
			return;
		}
		MouseAdapter mouseAdapter = new MouseAdapter()
		{
			@Override
			public void mouseExited(MouseEvent e)
			{
				if (c.isEnabled())
				{
					if (e.getX() <= 0 || e.getY() <= 0
							|| e.getX() >= c.getWidth()
							|| e.getY() >= c.getHeight())
						c.setBorder(getDefaultBorder());
				}
			}
			@Override
			public void mouseEntered(MouseEvent e)
			{
				if (c.isEnabled())
					c.setBorder(getActiveBorder());
			}
		};
		c.addMouseListener(mouseAdapter);
	}

	public static void setToActiveBorder2(final JComponent c, int innerSpace)
	{
		if (c == null)
			return;
		final RoundBorder rBorder = new RoundBorder(innerSpace, defaultArc);
		c.setBorder(rBorder);
		if (c instanceof JMouseActComp)
		{
			((JMouseActComp) c).setRolloverEnable(true);
			return;
		}
		MouseAdapter mouseAdapter = new MouseAdapter()
		{
			@Override
			public void mouseExited(MouseEvent e)
			{
				if (c.isEnabled() && (e.getX() <= 0 || e.getY() <= 0
						|| e.getX() >= c.getWidth()
						|| e.getY() >= c.getHeight()))
				{
					rBorder.setActivated(false);
					c.repaint();
				}
			}
			@Override
			public void mouseEntered(MouseEvent e)
			{
				if (c.isEnabled() && !rBorder.isActivated())
				{
					rBorder.setActivated(true);
					c.repaint();
				}
			}
		};
		c.addMouseListener(mouseAdapter);
	}

	public static Border getNoShadowBorder()
	{
		if (noShadowBorder == null)
			noShadowBorder = new RoundBorder(0, 10, 0);
		return noShadowBorder;
	}

	public static Border getFocusBorder()
	{
		if (focusBorder == null)
			focusBorder = new FocusBorder();
		return focusBorder;
	}

	public static Border getTextBorder()
	{
		if (emptyBorder == null)
			emptyBorder = new EmptyBorder(TEXT_INSETS);
		return emptyBorder;
	}

	public static Border getSimpleRoundBorder()
	{
		if (simpleRoundBorder == null)
			simpleRoundBorder = new FocusRoundBorder();
		return simpleRoundBorder;
	}

	public static Border createRoundBorder()
	{
		return new RoundBorder();
	}

	public static Border createRoundBorder(int round)
	{
		return new RoundBorder(round);
	}

	public static Border createRoundBorder(int innerSpace, int round)
	{
		return new RoundBorder(innerSpace, round);
	}

	public static Border createRoundBorder(int innerSpace, int round,
			int shadowSize)
	{
		return new RoundBorder(innerSpace, round, shadowSize);
	}

	public static Border createButtonBorder()
	{
		return new RButtonBorder();
	}

	public static Border createIconButtonBorder()
	{
		return new IconButtonBorder();
	}

	public static Border createSimpleRoundBorder(Stroke stroke)
	{
		return new SimpleRoundBorder(stroke);
	}

	public static Border createFocusRoundBorder(RFocusManager focusManager)
	{
		return new FocusRoundBorder(focusManager);
	}

	public static Border createTitleBorder(Icon aIcon, String aTitle,
			int aShadowSize, int aInnerSpace)
	{
		return new RichTitleBorder(aIcon, aTitle, aShadowSize, aInnerSpace);
	}

	public static class RoundBorder implements Border
	{
		private int arc = defaultArc;
		private int innerSpace = 4;
		private int shadowSize = 2;
		private final Color shadowColor = Color.black;
		private final int shadowOpacity = 96;
		private Color[] shadowColors;
		private final Color hiColor = new Color(255, 255, 255, 220);
		// private Color loColor = new Color(130, 130, 130, 220);
		private Insets borderInsets = null;
		private boolean activated;
		private final RenderingConfig renderingConfig = new RenderingConfig();

		public RoundBorder()
		{
			borderInsets = new Insets(innerSpace, innerSpace,
					innerSpace + shadowSize, innerSpace + shadowSize);
			initShadow(shadowSize);
		}

		public RoundBorder(int arc)
		{
			this.arc = arc;
			borderInsets = new Insets(innerSpace, innerSpace,
					innerSpace + shadowSize, innerSpace + shadowSize);
			initShadow(shadowSize);
		}

		public RoundBorder(int arc, boolean activated)
		{
			this(arc);
			this.activated = activated;
		}

		public RoundBorder(int innerSpace, int arc)
		{
			this(innerSpace, arc, 3);
		}

		public RoundBorder(int innerSpace, int arc, int shadowSize)
		{
			if (innerSpace < 2)
				innerSpace = 2;
			this.innerSpace = innerSpace;
			this.arc = arc;
			this.shadowSize = shadowSize;
			if (shadowSize > 0)
				initShadow(shadowSize);
			borderInsets = new Insets(innerSpace, innerSpace,
					innerSpace + shadowSize, innerSpace + shadowSize);
		}

		public void setShadowSize(int shadowSize)
		{
			if (shadowSize < 0 || this.shadowSize == shadowSize)
				return;
			this.shadowSize = shadowSize;
			if (shadowSize > 0)
				initShadow(shadowSize);
		}

		private void initShadow(int shadowSize)
		{
			shadowColors = new Color[shadowSize];
			int opacity = shadowOpacity;
			int r = shadowColor.getRed();
			int g = shadowColor.getGreen();
			int b = shadowColor.getBlue();
			for (int i = 0; i < shadowSize; i++)
			{
				shadowColors[i] = new Color(r, g, b, opacity);
				opacity = opacity / 2;
			}
		}

		public void setActivated(boolean activated)
		{
			this.activated = activated;
		}

		public boolean isActivated()
		{
			return activated;
		}

		@Override
		public void paintBorder(Component c, Graphics g, int x, int y, int w,
				int h)
		{
			Graphics2D g2d = (Graphics2D) g;
			renderingConfig.setAAPainting(g2d);
			if (shadowSize > 0)
			{
				int cc = arc / 2 + 4;
				int xx = x + cc, yy = y + cc;
				int ww = w - shadowSize - cc, hh = h - shadowSize - cc;
				int rr = arc + 2;
				for (int i = 0; i < shadowSize; i++)
				{
					g2d.setColor(shadowColors[i]);
					GraphicsTools.drawBottomRightRoundRect(g2d, xx, yy, ww + i,
							hh + i, rr);
				}
			}
			if (c instanceof JMouseActComp)
			{
				activated = ((JMouseActComp) c).isActivated();
				System.out.println("isActivated:" + activated);
			}
			if (activated)
				g2d.setColor(RThemeColor.themeColor);
			else if (UITools.isLight())
			{
				//				g2d.setColor(loColor);
				//				g2d.drawRoundRect(x, y, w - shadowSize - 1, h - shadowSize - 1,
				//						arc + 3, arc + 3);
				g2d.setColor(hiColor);
			} else
				g2d.setColor(RThemeColor.colorSet.borderColor);
			Stroke saveStroke = g2d.getStroke();
			g2d.setStroke(GraphicsTools.getLargeStroke());
			g2d.drawRoundRect(x + 1, y + 1, w - shadowSize - 3,
					h - shadowSize - 3, arc, arc);
			g2d.setStroke(saveStroke);
			renderingConfig.resetAAPainting(g2d);
		}

		@Override
		public Insets getBorderInsets(Component c)
		{
			if (borderInsets != null)
				return borderInsets;
			return new Insets(0, 0, 0, 0);
		}

		@Override
		public boolean isBorderOpaque()
		{
			return false;
		}
	}

	public static class FocusRoundBorder extends SimpleRoundBorder
	{
		private RFocusManager focusManager;

		public FocusRoundBorder()
		{
		}

		public FocusRoundBorder(RFocusManager focusManager)
		{
			this.focusManager = focusManager;
		}

		public FocusRoundBorder(Stroke stroke)
		{
			setStroke(stroke);
		}

		public FocusRoundBorder(Stroke stroke, int arc)
		{
			setStroke(stroke);
			this.arc = arc;
		}

		public void setFocusManager(RFocusManager focusManager)
		{
			this.focusManager = focusManager;
		}

		@Override
		public void paint(Graphics g, int x, int y, int width, int height)
		{
			if (focusManager != null && focusManager.isFocus)
				g.setColor(RThemeColor.themeColor);
			else
				g.setColor(RThemeColor.colorSet.borderColor);
			g.drawRoundRect(x + 1, y + 1, width - 2, height - 2, arc, arc);
		}

	}

	public static class SimpleRoundBorder implements Border
	{
		private Stroke stroke;
		protected int arc = defaultArc;
		private boolean activated;
		private final Insets insets = new Insets(1, 1, 1, 1);

		public SimpleRoundBorder()
		{
		}

		public SimpleRoundBorder(int arc)
		{
			this.arc = arc;
		}

		public SimpleRoundBorder(Stroke stroke)
		{
			setStroke(stroke);
		}

		public SimpleRoundBorder(Stroke stroke, int arc)
		{
			setStroke(stroke);
			this.arc = arc;
		}

		public SimpleRoundBorder(boolean activated, int arc)
		{
			this.activated = activated;
			this.arc = arc;
		}

		public void setStroke(Stroke stroke)
		{
			this.stroke = stroke;
			if (stroke instanceof BasicStroke)
			{
				int width = (int) ((BasicStroke) stroke).getLineWidth();
				if (width <= 0)
					width = 1;
				insets.left = insets.right = insets.top = insets.bottom = width;
			}
		}

		public void setActivated(boolean activated)
		{
			this.activated = activated;
		}

		@Override
		public void paintBorder(Component c, Graphics g, int x, int y,
				int width, int height)
		{
			Graphics2D g2d = (Graphics2D) g;
			Object savedReorderingHint = g2d
					.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
			g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
			Stroke saveStroke = g2d.getStroke();
			if (stroke != null)
				g2d.setStroke(stroke);
			paint(g2d, x, y, width, height);
			g2d.setStroke(saveStroke);
			g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					savedReorderingHint);
		}

		public void paint(Graphics g, int x, int y, int width, int height)
		{
			if (activated)
				g.setColor(RThemeColor.themeColor);
			else
				g.setColor(RThemeColor.colorSet.borderColor);
			g.drawRoundRect(x + 1, y + 1, width - 2, height - 2, arc, arc);
		}

		@Override
		public Insets getBorderInsets(Component c)
		{
			return insets;
		}

		@Override
		public boolean isBorderOpaque()
		{
			return false;
		}
	}

	public static class RButtonBorder implements Border, UIResource
	{
		private static final Insets insets = new Insets(6, 12, 6, 12);

		@Override
		public void paintBorder(Component c, Graphics g, int x, int y,
				int width, int height)
		{
		}

		@Override
		public Insets getBorderInsets(Component c)
		{
			return insets;
		}

		@Override
		public boolean isBorderOpaque()
		{
			return false;
		}
	}

	public static class IconButtonBorder implements Border, UIResource
	{
		private static final Insets insets = new Insets(5, 6, 5, 6);

		@Override
		public void paintBorder(Component c, Graphics g, int x, int y,
				int width, int height)
		{
		}

		@Override
		public Insets getBorderInsets(Component c)
		{
			return insets;
		}

		@Override
		public boolean isBorderOpaque()
		{
			return false;
		}
	}

	public static class FocusBorder implements Border, UIResource
	{
		@Override
		public void paintBorder(Component c, Graphics g, int x, int y,
				int width, int height)
		{
			g.setColor(RThemeColor.themeColor);
			g.drawRoundRect(x, y, width, height, 5, 5);
			g.drawRoundRect(x + 1, y + 1, width - 2, height - 2, 5, 5);
		}

		@Override
		public Insets getBorderInsets(Component c)
		{
			return TEXT_INSETS;
		}

		@Override
		public boolean isBorderOpaque()
		{
			return false;
		}
	}

	public static class EmptyBorder implements Border, UIResource
	{
		private final Insets insets;

		public EmptyBorder(Insets insets)
		{
			this.insets = insets;
		}

		public EmptyBorder(int top, int left, int bottom, int right)
		{
			insets = new Insets(top, left, bottom, right);
		}

		@Override
		public void paintBorder(Component c, Graphics g, int x, int y,
				int width, int height)
		{
		}

		@Override
		public Insets getBorderInsets(Component c)
		{
			return insets;
		}

		@Override
		public boolean isBorderOpaque()
		{
			return false;
		}
	}

	public static class EtchedBorder extends AbstractBorder
	{
		private static final long serialVersionUID = -6747068958959801339L;
		public static final int RAISED = 0;
		public static final int LOWERED = 1;

		protected int etchType;
		protected Color highlight;
		protected Color shadow;

		public EtchedBorder()
		{
			this(LOWERED);
		}

		public EtchedBorder(int etchType)
		{
			this(etchType, null, null);
		}

		public EtchedBorder(Color highlight, Color shadow)
		{
			this(LOWERED, highlight, shadow);
		}

		public EtchedBorder(int etchType, Color highlight, Color shadow)
		{
			this.etchType = etchType;
			this.highlight = highlight;
			this.shadow = shadow;
		}

		@Override
		public void paintBorder(Component c, Graphics g, int x, int y,
				int width, int height)
		{
			g.translate(x, y);

			g.setColor(etchType == LOWERED
					? getShadowColor(c)
					: getHighlightColor(c));
			g.drawRect(0, 0, width - 2, height - 2);

			g.setColor(etchType == LOWERED
					? getHighlightColor(c)
					: getShadowColor(c));
			g.drawLine(1, height - 3, 1, 1);
			g.drawLine(1, 1, width - 3, 1);

			g.drawLine(0, height - 1, width - 1, height - 1);
			g.drawLine(width - 1, height - 1, width - 1, 0);

			g.translate(-x, -y);
		}

		@Override
		public Insets getBorderInsets(Component c, Insets insets)
		{
			insets.set(2, 2, 2, 2);
			return insets;
		}

		@Override
		public boolean isBorderOpaque()
		{
			return true;
		}

		public int getEtchType()
		{
			return etchType;
		}

		public Color getHighlightColor(Component c)
		{
			return highlight != null ? highlight : c.getBackground().brighter();
		}

		public Color getHighlightColor()
		{
			return highlight;
		}

		public Color getShadowColor(Component c)
		{
			return shadow != null ? shadow : c.getBackground().darker();
		}

		public Color getShadowColor()
		{
			return shadow;
		}
	}
}
